package br.com.ema.maze.components;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import br.com.ema.maze.agents.MazeCharacter;
import br.com.ema.maze.enums.Direction;

/**
 * @author Emanuel Cruz Rodrigues -> emanuelcruzrodrigues@gmail.com
 *
 */
public class MazeSpace extends MazeComponent{
	
	private Maze maze;
	private Coordinates coordinates;
	private MazeDecoration decoration;
	private Map<Direction, MazeSpace> nearbySpaces;
	private List<MazeCharacter> characters;
	private Map<MazeCharacter, List<Long>> characterPassCount;
	private Color winnerColor;
	
	public MazeSpace(int x, int y) {
		this(new Coordinates(x, y));
	}
	
	public MazeSpace(Coordinates coordinates) {
		this.coordinates = coordinates;
		nearbySpaces = new HashMap<Direction, MazeSpace>(4);
		characterPassCount = new HashMap<MazeCharacter, List<Long>>();
		this.characters = new ArrayList<MazeCharacter>();
	}

	public Coordinates getCoordinates() {
		return coordinates;
	}
	
	public MazeDecoration getDecoration() {
		return decoration;
	}
	public MazeSpace setDecoration(MazeDecoration decoration) {
		this.decoration = decoration;
		if (decoration != null){
			decoration.setMazeComponent(this);
		}
		return this;
	}

	public int getX() {
		return coordinates.getX();
	}

	public int getY() {
		return coordinates.getY();
	}
	
	public void addNearbySpace(MazeSpace anotherSpace, Direction direction) {
		nearbySpaces.put(direction, anotherSpace);
	}
	public MazeSpace getNearbySpace(Direction direction) {
		MazeSpace space = nearbySpaces.get(direction);
		return ( space != null && space.allowsPassage() ) ? space : null;
	}
	
	public boolean allowsPassage(){
		return getDecoration() == null || getDecoration().allowsPassage();
	}

	public List<MazeSpace> getPossibleRoutes() {
		List<MazeSpace> spaces = new ArrayList<MazeSpace>();
		for (MazeSpace space : nearbySpaces.values()) {
			if (space != null && space.allowsPassage()){
				spaces.add(space);
			}
		}
		return spaces;
	}

	public boolean hasDecoration(){
		return getDecoration() != null;		
	}
	
	public boolean hasWalls() {
		return hasDecoration() && getDecoration() instanceof MazeWall;
	}

	public void putCharacter(MazeCharacter character) {
		characters.add(character);
		updateCharacterPassCount(character);
		callContentChangeListeners();
	}
	
	public void removeCharacter(MazeCharacter character) {
		characters.remove(character);
		callContentChangeListeners();
	}

	public boolean hasCharacterOn() {
		return characters.size() != 0;
	}


	private void updateCharacterPassCount(MazeCharacter character) {
		List<Long> passages = characterPassCount.get(character);
		if (passages == null){
			passages = new ArrayList<Long>();
		}
		passages.add(Calendar.getInstance().getTimeInMillis());
		characterPassCount.put(character, passages);
	}

	public double getSpaceDistance(MazeSpace space) {
		if (space == null) return 0D;
		
		int xDifference = Math.abs(space.getX() - getX());
		xDifference = xDifference * xDifference;
		
		int yDifference = Math.abs(space.getY() - getY());
		yDifference = yDifference * yDifference;
		
		return Math.sqrt(xDifference + yDifference);
	}

	public MazeSpace getMinRoute(MazeSpace destination) {
		List<MazeSpace> possibleRoutes = getPossibleRoutes();
		double min = Double.MAX_VALUE;
		MazeSpace minRoute = null;
		for (MazeSpace mazeSpace : possibleRoutes) {
			if (mazeSpace != null ){
				double distance = mazeSpace.getSpaceDistance(destination);
				if (distance <= min){
					min = distance;
					minRoute = mazeSpace;
				}
			}
		}
		return minRoute;
	}

	public int getTimesPassed(MazeCharacter character) {
		return getTimesPassed(character, 0);
	}
	
	public int getTimesPassed(MazeCharacter character, long miliSecondsPast) {
		List<Long> timesPassed = characterPassCount.get(character);
		if (timesPassed == null) return 0;
		if (miliSecondsPast == 0){
			return timesPassed.size();
		}else{
			long timeInMillis = Calendar.getInstance().getTimeInMillis() - miliSecondsPast;
			int count = 0;
			for (Long hourPassed : timesPassed) {
				if (hourPassed >= timeInMillis){
					count++;
				}
			}
			return count;
		}
	}
	
	public void resetTimesPassed(){
		characterPassCount.clear();
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("MazeSpace [coordinates=");
		builder.append(coordinates);
		builder.append("]");
		return builder.toString();
	}

	public boolean isExit() {
		return getDecoration() != null && getDecoration() instanceof MazeExit;
	}
	
	public MazeSpace getMinOriginalRoute(MazeCharacter character, MazeSpace destination) {
		return getMinOriginalRoute(character, destination, 0);
	}

	public MazeSpace getMinOriginalRoute(MazeCharacter character, MazeSpace destination, long interval) {
		List<MazeSpace> possibleRoutes = getPossibleRoutes();
		double min = Double.MAX_VALUE;
		MazeSpace minRoute = null;
		for (MazeSpace mazeSpace : possibleRoutes) {
			if (mazeSpace != null ){
				double timesPassed = mazeSpace.getTimesPassed(character, interval);
				double distance = mazeSpace.getSpaceDistance(destination) * (timesPassed+1);
				if (distance <= min){
					min = distance;
					minRoute = mazeSpace;
				}
			}
		}
		return minRoute;
	}
	
	public List<MazeCharacter> getCharacters() {
		return characters;
	}

	public boolean isWinnerPassage() {
		return winnerColor != null;
	}

	public void setWinnerColor(Color winnerColor) {
		this.winnerColor = winnerColor;
	}
	
	public Color getWinnerColor() {
		return winnerColor;
	}

	public Maze getMaze() {
		return maze;
	}

	public void setMaze(Maze maze) {
		this.maze = maze;
	}

	public double getMinimalCharactersDistance(){
		Collection<MazeCharacter> characters = maze.getCharacters();
		double distance = Double.MAX_VALUE;
		for (MazeCharacter mazeCharacter : characters) {
			MazeSpace actualSpace = mazeCharacter.getActualSpace();
			if (actualSpace != null ){
				double spaceDistance = actualSpace.getSpaceDistance(this);
				if (spaceDistance < distance){
					distance = spaceDistance;
				}
			}
		}
		return distance;
	}
	
}
